﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Sustainalytics.DataLayer;
using Sustainalytics.DataLayer.ConnectionStrings;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESGRatings.BusinessLogic.Exceptions;
using Sustainalytics.Utils;

namespace Sustainalytics.ESGRatings.BusinessLogic.ESGProfile
{
    public class ESGProfileManagement : IESGProfileManagement
    {
        private readonly IRepositoryCollection<Entities.ESGRatings.ESGProfile> _esgProfileRepository;
        private readonly IRepositoryCollection<ReferenceUniverse>   _referenceUniverseRepository;
        private readonly IRepositoryCollection<WeightMatrix> _weightMatrixRepository;
        private IAccountsRepository _accountsRepository;
        private bool _verbosity;
        private IList<string> _messages;


        public static ESGProfileManagement CreateProfileManagement()
        {

            var clientPlatformCnx = ConfigurationManager.ConnectionStrings["ClientPlatform"].ConnectionString;
            var connectionStringsManager =
                ConnectionStringsManager.GetInstance(ConnectionStringsRepository.GetInstance(clientPlatformCnx));
            var clientDwCnx = connectionStringsManager.GetConnectionString(ConnectionStringType.ClientDW);

            var refUnivRep = new RepositoryCollection<ReferenceUniverse>(clientDwCnx);
            var weightMatrixRep = new RepositoryCollection<WeightMatrix>(clientDwCnx);
            var esgProfileRep = new RepositoryCollection<Entities.ESGRatings.ESGProfile>(clientPlatformCnx, "ESGProfile");
            
            return new ESGProfileManagement(esgProfileRep, refUnivRep, weightMatrixRep);
        }


        public ESGProfileManagement(IRepositoryCollection<Entities.ESGRatings.ESGProfile> esgRepository, IRepositoryCollection<ReferenceUniverse> referenceUniverseRepository, IRepositoryCollection<WeightMatrix> weightMatrixRepository)
        {
            _esgProfileRepository = esgRepository;
            _referenceUniverseRepository = referenceUniverseRepository;
            _weightMatrixRepository = weightMatrixRepository;

            if (_accountsRepository == null)
            {
                var clientPlatformCnx = ConfigurationManager.ConnectionStrings["ClientPlatform"].ConnectionString;
                SetAccountRepository(AccountsRepository.GetAccountsRepository(clientPlatformCnx));
            }
            
        }

        public void SetAccountRepository(IAccountsRepository accountRepository)
        {
            _accountsRepository = accountRepository;
        }

        public IList<string> UpdateEsgProfiles(bool verbose=false)
        {
            _verbosity = verbose;
            _messages = new List<string>();

            var accounts = _accountsRepository.GeAccountsByUpcomingEsgProfiles();
            foreach (var account in accounts)
            {
                UpdateMethod(string.Format("Beginning processing the account {0} which has the current esg profile {1} and will be replaced with {2}", account.Id, account.CurrentEsgProfile, account.UpcomingEsgProfile));
                if (account.UpcomingEsgProfile == null) throw new InvalidOperationException(string.Format("The account {0} does'not have an upcoming esg profile", account.Id));
                UpdateMethod("Changing the active profile");
                ReplaceActiveProfileOfAccount(account.Id, account.CurrentEsgProfile,
                    account.UpcomingEsgProfile);
                UpdateMethod("Changing the active profile was succesful");
                UpdateMethod("Changing the esg profile active");
                _accountsRepository.UpdateCurrentEsgProfile(account);
                UpdateMethod("Changing the esg profile active was succesfull");
                UpdateMethod(string.Format("The account {0} was succesful updated of the esg profile", account.Id));
            }
            return _messages;
        }

        private void UpdateMethod(string msg)
        {
            if (_verbosity) return;
            _messages.Add(msg);
        }

        public List<Entities.ESGRatings.ESGProfile> LoadESGProfiles (Guid? accountId)
        {
            List<Entities.ESGRatings.ESGProfile> esgProfileData = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId).ToList();

            if (esgProfileData.Any())
            {             
                
                List<Entities.ESGRatings.ESGProfile> esgProfileDataUpdated = new List<Entities.ESGRatings.ESGProfile>();                

                foreach(var esgProfileItem in esgProfileData)
                {
                    esgProfileDataUpdated.Add(esgProfileItem);              

                    List<WeightMatrix> matricesMatch = _weightMatrixRepository.ReadWhere(x => x.Id == esgProfileItem.MatrixId).ToList();
                    List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfileItem.ReferenceUniverseId).ToList();

                    if (matricesMatch.Any() && refUnviversesMatch.Any())
                    {
                        WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                        ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                        //Fill with updated values
                        esgProfileItem.MatrixName = matrixMatch.Name;
                        esgProfileItem.ReferenceUniverseName = refUnivMatch.Name;                                 
                    }
                }               

                return esgProfileDataUpdated;
            }

            return new List<Entities.ESGRatings.ESGProfile>();
        }

        public List<Entities.ESGRatings.ESGProfile> LoadESGProfiles(IEnumerable<Guid> accountIds)
        {
            List<Entities.ESGRatings.ESGProfile> result = _esgProfileRepository.ReadAllWhere(a => accountIds.Contains(a.Id)).ToList();

            //Get the updated names for Matrix Name and Reference Universe Name, considering the persisted Id for them
            if (result.Any())
            {
                List<Entities.ESGRatings.ESGProfile> esgProfileDataUpdated = new List<Entities.ESGRatings.ESGProfile>();

                foreach (var esgProfileItem in result)
                {
                    esgProfileDataUpdated.Add(esgProfileItem);

                    List<WeightMatrix> matricesMatch = _weightMatrixRepository.ReadWhere(x => x.Id == esgProfileItem.MatrixId).ToList();
                    List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfileItem.ReferenceUniverseId).ToList();

                    if (matricesMatch.Any() && refUnviversesMatch.Any())
                    {
                        WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                        ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                        //Fill with updated values
                        esgProfileItem.MatrixName = matrixMatch.Name;
                        esgProfileItem.ReferenceUniverseName = refUnivMatch.Name;
                    }
                }

                return esgProfileDataUpdated;
            }

            //No result => return empty List
            return new List<Entities.ESGRatings.ESGProfile>();
        }

        public List<Entities.ESGRatings.ESGProfile> LoadESGProfiles(Guid? accountId, ListOptions options)
        {
            List<Entities.ESGRatings.ESGProfile> esgProfileData = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId, options.Skip, options.Take).ToList();

            if (esgProfileData.Any())
            {

                List<Entities.ESGRatings.ESGProfile> esgProfileDataUpdated = new List<Entities.ESGRatings.ESGProfile>();

                foreach (var esgProfileItem in esgProfileData)
                {
                    esgProfileDataUpdated.Add(esgProfileItem);

                    List<WeightMatrix> matricesMatch = _weightMatrixRepository.ReadWhere(x => x.Id == esgProfileItem.MatrixId).ToList();
                    List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfileItem.ReferenceUniverseId).ToList();

                    if (matricesMatch.Any() && refUnviversesMatch.Any())
                    {
                        WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                        ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                        //Fill with updated values
                        esgProfileItem.MatrixName = matrixMatch.Name;
                        esgProfileItem.ReferenceUniverseName = refUnivMatch.Name;                        
                    }
                }

                return esgProfileDataUpdated;
            }

            return esgProfileData;
        }

        public Entities.ESGRatings.ESGProfile LoadESGProfile(Guid esgProfileId )
        {
            List<Entities.ESGRatings.ESGProfile> esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.Id == esgProfileId).ToList();

            if (esgProfileEntities.Any())
            {
                Entities.ESGRatings.ESGProfile esgProfile = esgProfileEntities.FirstOrDefault();

                List<WeightMatrix> matricesMatch = _weightMatrixRepository.ReadWhere(x => x.Id == esgProfile.MatrixId).ToList();
                List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfile.ReferenceUniverseId).ToList();

                if (matricesMatch.Any() && refUnviversesMatch.Any())
                {
                    WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                    ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                    esgProfile.MatrixName = matrixMatch.Name;
                    esgProfile.ReferenceUniverseName = refUnivMatch.Name;

                    return esgProfile;
                }

                //Return ESG Profile in the version with not up-to-date  matrix name and reference universe name
                return esgProfile;
            }
            return null;
        }

        public Entities.ESGRatings.ESGProfile LoadESGProfileActiveState(Guid? accountId)
        {
            List<Entities.ESGRatings.ESGProfile> esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId && x.Active == ActiveTypeEnum.Active).ToList();
           
            if (esgProfileEntities.Any())
            {
                Entities.ESGRatings.ESGProfile esgProfile = esgProfileEntities.FirstOrDefault();

                List<WeightMatrix>  matricesMatch =  _weightMatrixRepository.ReadWhere(x => x.Id == esgProfile.MatrixId).ToList();
                List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfile.ReferenceUniverseId).ToList();

                if (matricesMatch.Any() && refUnviversesMatch.Any())
                {
                    WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                    ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                    esgProfile.MatrixName = matrixMatch.Name;
                    esgProfile.ReferenceUniverseName = refUnivMatch.Name;

                    return esgProfile;
                }

                //Return ESG Profile in the version with not up-to-date  matrix name and reference universe name
                return esgProfile;
            }

            return null;
        }

        public Entities.ESGRatings.ESGProfile AssureEsgProfile()
        {
            var defaultProfile = LoadDefaultEsgProfile();
            if (defaultProfile == null)
                return null;
            _esgProfileRepository.CreateOrUpdate(defaultProfile);
            return defaultProfile;
        }

        public Entities.ESGRatings.ESGProfile LoadDefaultEsgProfile()
        {
            Entities.ESGRatings.ESGProfile defaultProfile = Entities.ESGRatings.ESGProfile.DefaultProfile;

            var esgDefaultProfileEntities = _esgProfileRepository.ReadWhere(x => x.AccountId == null && x.Name == defaultProfile.Name,0,1);

            if (esgDefaultProfileEntities.IsNullOrEmpty())
                                            return null;

            var esgProfileDefault = esgDefaultProfileEntities.FirstOrDefault();

            if (esgProfileDefault!=null)
                                   return esgProfileDefault;

            esgProfileDefault = defaultProfile;
         
            var matricesEntities =
                _weightMatrixRepository.ReadWhere( x => (x.MarkedForDeletionOn == null) || (x.Name == defaultProfile.MatrixName && x.MarkedForDeletionOn == null) || (x.Id == defaultProfile.MatrixId), null, null, null, x => x.Name, x => x.Id);

            if (!matricesEntities.Any())
            {
                return esgProfileDefault;
            }
            var matrixDefault = matricesEntities.FirstOrDefault();

            if (matrixDefault == null)
            {
                return null;
            }

            esgProfileDefault.MatrixId = matrixDefault.Id;
            esgProfileDefault.MatrixName = matrixDefault.Name;


            var referenceUnivDefaultEntities = _referenceUniverseRepository.ReadWhere( x => (x.Name == esgProfileDefault.ReferenceUniverseName || x.Id == esgProfileDefault.ReferenceUniverseId), null, null, null, x => x.Id, x => x.Name) ?? _referenceUniverseRepository.ReadWhere(x => x.Name != string.Empty, null, null, null, x => x.Id, x => x.Name);
            var referenceUnivDefault = referenceUnivDefaultEntities.FirstOrDefault();

            if (referenceUnivDefault == null)
            {
                return null;
            }

            esgProfileDefault.ReferenceUniverseId = referenceUnivDefault.Id;
            esgProfileDefault.ReferenceUniverseName = referenceUnivDefault.Name;
            return esgProfileDefault;
        }

        public void ReplaceActiveProfileOfAccount(Guid accountId, Guid currentEsgProfile, Guid? upcomingEsgProfile)
        {
            if(!upcomingEsgProfile.HasValue) throw new ArgumentException("The upcoming esgprofile can not be null");
            if(upcomingEsgProfile.GetValueOrDefault()==Guid.Empty) throw new ArgumentException("The upcoming esgprofile guid is empty");
            var profiles = _esgProfileRepository.ReadAllWhere(a => a.AccountId == accountId).ToList();
            if(!profiles.Any()) throw new EsgProfileObjectNullException(string.Format("The account {0} does not have esg profiles",accountId.ToString()));
            if(profiles.All(a=>a.Id!=currentEsgProfile)) throw new ArgumentException(string.Format("The ESGProfile {0} guid is not associated to the account {1}",accountId,currentEsgProfile));
            if(profiles.All(c=>c.Id!=upcomingEsgProfile)) throw new ArgumentException(string.Format("The ESGProfile {0} guid is not associated to the upcoming account {1}",accountId,upcomingEsgProfile));
            var currentProfile = profiles.Find(a => a.Id == currentEsgProfile);
            currentProfile.Active=ActiveTypeEnum.NotActive;
            currentProfile.LastModifiedDate = DateTime.Now;
            _esgProfileRepository.Update(currentEsgProfile, currentProfile);
            var upcomingProfile = profiles.Find(a => a.Id == upcomingEsgProfile);
            upcomingProfile.Active=ActiveTypeEnum.Active;
            upcomingProfile.LastModifiedDate = DateTime.Now;
            _esgProfileRepository.Update(upcomingEsgProfile.Value, upcomingProfile);

        }

        public Entities.ESGRatings.ESGProfile LoadESGProfileSuggestedState(Guid? accountId)
        {
            IList<Entities.ESGRatings.ESGProfile> esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId && x.Active == ActiveTypeEnum.Suggested).ToList();

            if (esgProfileEntities.Any())
            {
                Entities.ESGRatings.ESGProfile esgProfile = esgProfileEntities.FirstOrDefault();

                List<WeightMatrix> matricesMatch = _weightMatrixRepository.ReadWhere(x => x.Id == esgProfile.MatrixId).ToList();
                List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfile.ReferenceUniverseId).ToList();

                if (matricesMatch.Any() && refUnviversesMatch.Any())
                {
                    WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                    ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                    esgProfile.MatrixName = matrixMatch.Name;
                    esgProfile.ReferenceUniverseName = refUnivMatch.Name;

                    return esgProfile;
                }

                //Return ESG Profile in the version with not up-to-date  matrix name and reference universe name
                return esgProfile;
            }
            
            return null;            
        }

        public Entities.ESGRatings.ESGProfile LoadESGProfileForAccount(Guid? accountId, Guid esgProfileId)
        {
            var esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId  && x.Id == esgProfileId).ToList();

            if (esgProfileEntities.Any())
            {
                Entities.ESGRatings.ESGProfile esgProfile = esgProfileEntities.FirstOrDefault();

                List<WeightMatrix> matricesMatch = _weightMatrixRepository.ReadWhere(x => x.Id == esgProfile.MatrixId).ToList();
                List<ReferenceUniverse> refUnviversesMatch = _referenceUniverseRepository.ReadWhere(x => x.Id == esgProfile.ReferenceUniverseId).ToList();

                if (matricesMatch.Any() && refUnviversesMatch.Any())
                {
                    WeightMatrix matrixMatch = matricesMatch.FirstOrDefault();
                    ReferenceUniverse refUnivMatch = refUnviversesMatch.FirstOrDefault();

                    esgProfile.MatrixName = matrixMatch.Name;
                    esgProfile.ReferenceUniverseName = refUnivMatch.Name;

                    return esgProfile;
                }

                //Return ESG Profile in the version with not up-to-date  matrix name and reference universe name
                return esgProfile;
            }

            return null;
        }

        public int CountESGProfilesNumber(Guid? accountId)
        {
            var profilesList = LoadESGProfiles(accountId);

            if (profilesList.Any())
            {
                return profilesList.Count;
            }

            return 0;
        }

        public void CreateESGProfile(Entities.ESGRatings.ESGProfile esgProfileEntity)
        {
            if (CountESGProfilesNumber(esgProfileEntity.AccountId) >= 20)
            {
                throw new EsgProfilesTooManyException();
            }
            _esgProfileRepository.Create(esgProfileEntity);
        }

        public void UpdateESGProfile(Guid? accountId, Guid esgProfileId,
            Entities.ESGRatings.ESGProfile esgProfileToUpdate)
        {
            var esgProfileEntities =
                _esgProfileRepository.ReadWhere(x => x.AccountId == accountId && x.Id == esgProfileId).ToList();
            if (esgProfileEntities.Any())
            {
                _esgProfileRepository.Update(esgProfileId, esgProfileToUpdate);
                if (esgProfileToUpdate.Active.HasValue && accountId.HasValue)
                {
                    var account = _accountsRepository.GetAccountById(accountId.Value);
                    if (account != null)
                    {
                        if (esgProfileToUpdate.Active.Value == ActiveTypeEnum.Suggested)
                        {
                            account.UpcomingEsgProfile = esgProfileId;
                        }
                        else
                        {
                            account.UpcomingEsgProfile = null;
                        }

                    }

                    _accountsRepository.SaveAccounts(new List<Account>() {account});
                }
            }

            else
            {
                throw new EsgProfileObjectNullException();
            }
        }

        public bool QueryExistentProfileActive(Guid? accountId)
        {
            List<Entities.ESGRatings.ESGProfile> esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId && x.Active == ActiveTypeEnum.Active).ToList();
            if (esgProfileEntities.Count > 1)
            {
                throw new EsgProfilesMultipleActiveException();
            }
            if (esgProfileEntities.Any() && esgProfileEntities.Count == 1)
            {
                return true;
            }
            return false;
        }

        public void DeleteESGProfileForAccount(Guid? accountId, Guid esgProfileId)
        {
            if (accountId == null)
            {
                throw new EsgProfileIllegalToDeleteException();
            }

            List<Entities.ESGRatings.ESGProfile> esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId && x.Id == esgProfileId).ToList();
            if (esgProfileEntities.Any())
            {
                _esgProfileRepository.Delete(esgProfileId);
            }
            else
            {
                throw new EsgProfileObjectNullException();
            }
        }

        public void DeleteESGProfile(Guid esgProfileId)
        {
            if (esgProfileId == null)
            {
                throw new EsgProfileObjectNullException();
            }

            List<Entities.ESGRatings.ESGProfile> esgProfileEntities = _esgProfileRepository.ReadWhere(x => x.Id == esgProfileId).ToList();
            if (esgProfileEntities.Any())
            {
                _esgProfileRepository.Delete(esgProfileId);
            }
            else
            {
                throw new EsgProfileObjectNullException();
            }
        }

        public void DeleteDefaultESGProfile(Guid? accountId, Guid esgProfileId)
        {
            if (accountId != null)
            {
                return;
            }
            List<Entities.ESGRatings.ESGProfile> esgDefaultProfileEntity = _esgProfileRepository.ReadWhere(x => x.AccountId == null && x.Id == esgProfileId).ToList();
            if (!esgDefaultProfileEntity.Any())
            {
                throw new EsgProfileObjectNullException();
            }
            _esgProfileRepository.Delete(esgProfileId);
          
        }

        public void DeleteAllESGProfilesForAccount(Guid? accountId)
        {
            if (accountId != null)
            {
                List<Entities.ESGRatings.ESGProfile> esgDefaultProfileEnties = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId).ToList();
                if (esgDefaultProfileEnties.Any())
                {
                    foreach (Entities.ESGRatings.ESGProfile entityProfile in esgDefaultProfileEnties)
                    {
                        Guid esgProfileId = entityProfile.Id;
                        _esgProfileRepository.Delete(esgProfileId);
                    }
                }
                else
                {
                    throw new EsgProfileObjectNullException();
                }
            }
            else
            {
                throw new EsgProfileIllegalToDeleteException();
            }
        }

        public bool FindEsgProfileNameDuplicate(string newEsgProfileName, Guid esgProfileId)
        {
            //If the ESG Profile name already exists distinctly from the ESG Profile with the given Id, it means it is a duplicate. This is to make the distinction between POST (Insert) and PUT (Edit) 
            List<Entities.ESGRatings.ESGProfile> esgDefaultProfileEnties = _esgProfileRepository.ReadWhere(x => x.Name.Equals(newEsgProfileName) && x.Id != esgProfileId).ToList();
            if (esgDefaultProfileEnties.Any())
            {
                return true;
            }
            return false;
        }

        public void DeleteAllIactiveESGProfilesForAccount(Guid? accountId, ActiveTypeEnum activeState)
        {
            if (accountId != null && activeState != ActiveTypeEnum.Active && activeState != ActiveTypeEnum.Suggested)
            {
                List<Entities.ESGRatings.ESGProfile> esgDefaultProfileEnties = _esgProfileRepository.ReadWhere(x => x.AccountId == accountId && x.Active == ActiveTypeEnum.NotActive).ToList();
                if (esgDefaultProfileEnties.Any())
                {
                    foreach (Entities.ESGRatings.ESGProfile entityProfile in esgDefaultProfileEnties)
                    {
                        Guid esgProfileId = entityProfile.Id;
                        _esgProfileRepository.Delete(esgProfileId);
                    }
                }
                else
                {
                    throw new EsgProfileObjectNullException();
                }
            }
            else
            {
                throw new EsgProfileIllegalToDeleteException();
            }
        }
    }
}
